// Archive extract module, added by regret
// use Blargg's File_Extractor (http://www.slack.net/~ant/)

/* changelog:
 update 6: add class ArchiveLoad
 update 5: change loadonefile() to use separate fex handle
 update 4: update to Blargg's File_Extractor 1.0.0
 update 3: rewrite interface, use Blargg's great File_Extractor
 update 2: add load one file from archive
 update 1: add 7zip aupport
*/

#include "burner.h"
#include "archive.h"

ArchiveLoad archiveLoad;

ArchiveLoad::ArchiveLoad() :
	fex(NULL), err(NULL), currentFile(0)
{
}

ArchiveLoad::~ArchiveLoad()
{
}

inline bool ArchiveLoad::error(const char* error)
{
	if (error) {
#ifdef _DEBUG
		printf("fex Error: %s\n", error);
#endif
		return true;
	}
	return false;
}

// check if input name has 7z or zip file, the name will link with extension
// return value: 0:zip; 1:7z; -1:none
int ArchiveLoad::check(char* name, int zipOnly)
{
	if (!name) {
		return ARC_NONE;
	}

	static char archiveName[MAX_PATH] = "";
	int ret = ARC_NONE;

	// try zip first
	sprintf(archiveName, "%s.zip", name);

#ifndef _WIN32
	static File_Extractor* fex_scan = NULL;
	static fex_err_t err_scan = NULL;

	err_scan = fex_open(&fex_scan, archiveName);
	if (!error(err_scan)) {
		ret = ARC_ZIP;
		strcat(name, ".zip");
	} else {
		if (!zipOnly) {
			// try 7z
			sprintf(archiveName, "%s.7z", name);

			err_scan = fex_open(&fex_scan, archiveName);
			if (!error(err_scan)) {
				ret = ARC_7Z;
				strcat(name, ".7z");
			}
		}
	}

	if (fex_scan) {
		fex_close(fex_scan);
		fex_scan = NULL;
	}
#else
	if (fileExistsA(archiveName)) {
		ret = ARC_ZIP;
		strcat(name, ".zip");
	} else {
		if (!zipOnly) {
			// try 7z
			sprintf(archiveName, "%s.7z", name);
			if (fileExistsA(archiveName)) {
				ret = ARC_7Z;
				strcat(name, ".7z");
			}
		}
	}
#endif

	return ret;
}

int ArchiveLoad::open(const char* archive)
{
	if (!archive) {
		return 1;
	}

	err = fex_open(&fex, archive);
	if (error(err)) {
		return 1;
	}

	currentFile = 0;
	return 0;
}

int ArchiveLoad::close()
{
	if (fex) {
		fex_close(fex);
		fex = NULL;
	}
	return 0;
}

// Get the contents of a archive file into an array of ArcEntry
int ArchiveLoad::getList(ArcEntry** list, unsigned* count)
{
	if (!fex || !list) {
		return 1;
	}

	unsigned listLen = 0;
	while (!fex_done(fex)) {
		err = fex_next(fex);
		if (error(err)) {
			close();
			return 1;
		}
		listLen++;
	}

	// Make an array of File Entries
	if (listLen == 0) {
		close();
		return 1;
	}

	ArcEntry* List = new ArcEntry[listLen];
	if (List == NULL) {
		close();
		return 1;
	}
	memset(List, 0, listLen * sizeof(ArcEntry));

	err = fex_rewind(fex);
	if (error(err)) {
		close();
		return 1;
	}

	// Step through all of the files, until we get to the end
	for (currentFile = 0, err = NULL;
		currentFile < listLen && !error(err);
		currentFile++, err = fex_next(fex))
	{
		fex_stat(fex);

		// Allocate space for the filename
		const char* name = fex_name(fex);
		if (name == NULL) continue;
		char* szName = new char[strlen(name) + 1];
		if (szName == NULL) continue;
		strcpy(szName, name);

		List[currentFile].szName = szName;
		List[currentFile].nLen = fex_size(fex);
		List[currentFile].nCrc = fex_crc32(fex);
	}

	// return the file list
	*list = List;
	if (count) {
		*count = listLen;
	}

	err = fex_rewind(fex);
	if (error(err)) {
		close();
		return 1;
	}

	currentFile = 0;
	return 0;
}

int ArchiveLoad::loadFile(unsigned char* dest, unsigned len, unsigned entry, unsigned* wrote)
{
	if (!fex || len <= 0) {
		return 1;
	}

//	if (entry < currentFile)
	{
		err = fex_rewind(fex);
		if (error(err)) {
			return 1;
		}
		currentFile = 0;
	}

	// Now step through to the file we need
	while (currentFile < entry) {
		err = fex_next(fex);
		if (error(err) || fex_done(fex)) {
			return 1;
		}
		currentFile++;
	}

	err = fex_read(fex, dest, len);
	if (error(err)) {
		return 1;
	}
	if (wrote != NULL) *wrote = len;

	return 0;
}

int ArchiveLoad::loadOneFile(const char* arc, const char* file, void** dest, unsigned* wrote)
{
	File_Extractor* fex_one = NULL;
	fex_err_t err_one = fex_open(&fex_one, arc);
	if (error(err_one)) {
		return 1;
	}

	unsigned listLen = 0;
	while (!fex_done(fex_one)) {
		err_one = fex_next(fex_one);
		if (error(err_one)) {
			fex_close(fex_one);
			return 1;
		}
		listLen++;
	}

	if (listLen <= 0) {
		fex_close(fex_one);
		return 1;
	}

	err_one = fex_rewind(fex_one);
	if (error(err_one)) {
		fex_close(fex_one);
		return 1;
	}

	if (file) {
		const char* filename = file;
		int currentFile = 0;

		for (currentFile = 0, err_one = NULL;
			currentFile < listLen && !error(err_one);
			currentFile++, err_one = fex_next(fex_one))
		{
			fex_stat(fex_one);

			const char* name = fex_name(fex_one);
			if (name == NULL) continue;
			if (!strcmp(name, filename)) {
				break;
			}
		}

		if (currentFile == listLen) {
			fex_close(fex_one);
			return 1; // didn't find
		}
	}

	fex_stat(fex_one);
	long len = fex_size(fex_one);

	if (*dest == NULL) {
		*dest = (unsigned char*)malloc(len);
		if (!*dest) {
			fex_close(fex_one);
			return 1;
		}
	}

	// Extract file
	err_one = fex_read(fex_one, *dest, len);
	if (wrote != NULL) *wrote = len;

	fex_close(fex_one);

	if (error(err_one)) {
		free(*dest);
		return 1;
	}
	return 0;
}
